/*
 * Copyright 2007 Transmogrify, LLC.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.xmog.web.handler;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import javax.servlet.http.HttpServletRequest;

import com.xmog.web.servlet.WebContext;
import com.xmog.web.util.ReflectionUtils;

/**
 * Encapsulates information related to a method that handles a URL. Knows how to
 * invoke the method via reflection and, with the help of {@link DataBinder},
 * marshals request data and binds it to method parameters as needed.
 * 
 * @author <a href="mailto:maa@xmog.com">Mark Allen</a>
 */
public class UrlHandler {
	private Method method;
	private Class<?>[] parameterTypes;
	private int parameterCount;

	// private static final Logger log = Logger.getLogger(UrlHandler.class);

	public UrlHandler(Method method) {
		if (method == null) {
			throw new IllegalArgumentException("The 'method' parameter is required for the "
					+ UrlHandler.class.getSimpleName() + " constructor.");
		}
		this.method = method;

		parameterTypes = method.getParameterTypes();
		parameterCount = parameterTypes.length;
	}

	public Object invoke(Object target) throws UrlHandlerExecutionException {
		if (target == null) {
			throw new UrlHandlerTargetMissingException();
		}

		Object result = null;

		try {
			result = method.invoke(target, buildParameters());
		} catch (IllegalAccessException e) {
			unexpectedHandlerExecutionException(e);
		} catch (InvocationTargetException e) {
			unexpectedHandlerExecutionException(e);
		} catch (ExceptionInInitializerError e) {
			unexpectedHandlerExecutionException(e);
		} catch (Throwable t) {
			throw new UrlHandlerExecutionException("An exception was thrown while executing " + method, t);
		}

		return result;
	}

	public Class<?> getDeclaringClass() {
		return method.getDeclaringClass();
	}

	/**
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	@Override
	public boolean equals(Object object) {
		if (!(object instanceof UrlHandler)) {
			return false;
		}
		return method.equals(((UrlHandler) object).method);
	}

	/**
	 * @see java.lang.Object#hashCode()
	 */
	@Override
	public int hashCode() {
		return 17 * method.hashCode();
	}

	/**
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString() {
		return ReflectionUtils.methodToString(method);
	}

	private Object[] buildParameters() {
		if (parameterCount == 0) {
			return new Object[] {};
		}

		Object[] parameters = new Object[parameterCount];

		for (int i = 0; i < parameterCount; i++) {
			if (parameterTypes[i].isAssignableFrom(HttpServletRequest.class)) {
				parameters[i] = WebContext.getRequest();
			} else {
				// TODO: finish up
				parameters[i] = null;
			}
		}

		return parameters;
	}

	private void unexpectedHandlerExecutionException(Throwable t)
			throws UrlHandlerExecutionUnexpectedException {
		throw new UrlHandlerExecutionUnexpectedException("An error occurred while attempting to invoke "
				+ toString(), t);
	}
}
